package org.example.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.example.vo.TenantVo;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @version V1.0
 * @description: 租户缓存类
 * @author: 宏伟数科-刘冬冬
 * @contact: QQ791156095
 * @date: 2020/3/20 13:40
 * @Copyright 该项目由宏伟数科版权所有，未经过允许的情况下，私自分享视频和源码属于违法行为。
 */
public class TenantCacheHolder implements InitializingBean {

    /**
     * 定时加载缓存调度
     */
    private static ScheduledExecutorService tenanTaskPool = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("").daemon(true).build());

    /**
     * 租户缓存前缀
     */
    public static final String TENANT_PREFIX = "tenant_";

    public static final String TENANT_KEY = "tenant_map";

    private final static ConcurrentHashMap<String, String> tenantLocalCache = new ConcurrentHashMap<>();

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void afterPropertiesSet() {
        tenanTaskPool.scheduleAtFixedRate(() -> loadLocalCache(), 5, 60 * 5, TimeUnit.SECONDS);
    }

    public void addTenant(List<TenantVo> tenantVos) {
        Map<String, Set<String>> tenantIdRedisDomainMap = redisKeyCacheTenantIdMap();
        try {
            redisTemplate.setEnableTransactionSupport(true);
            redisTemplate.multi();
            Map<String, String> tenantMap = Maps.newConcurrentMap();
            for (TenantVo tenantVo : tenantVos) {
                putCache(tenantVo, tenantMap, tenantIdRedisDomainMap);
            }
            if (!tenantMap.isEmpty()) {
                redisTemplate.opsForHash().putAll(TENANT_KEY, tenantMap);
                tenantLocalCache.putAll(tenantMap);
            }
            redisTemplate.exec();
        } catch (Exception e) {
            redisTemplate.discard();
            throw new RuntimeException(e);
        }
    }

    public void addTenant(TenantVo tenantVo) {
        Map<String, Set<String>> tenantIdRedisDomainMap = redisKeyCacheTenantIdMap();
        try {
            redisTemplate.setEnableTransactionSupport(true);
            redisTemplate.multi();
            if (tenantVo == null) {
                return;
            }
            Map<String, String> tenantMap = Maps.newConcurrentMap();
            putCache(tenantVo, tenantMap, tenantIdRedisDomainMap);
            if (!tenantMap.isEmpty()) {
                redisTemplate.opsForHash().putAll(TENANT_KEY, tenantMap);
                tenantLocalCache.putAll(tenantMap);
            }
            redisTemplate.exec();
        } catch (Exception e) {
            redisTemplate.discard();
            throw new RuntimeException(e);
        }
    }

    /**
     * 将redis中的租户信息清除，并将租户信息存进map中
     * @param tenantVo
     * @param tenantMap
     * @param tenantIdRedisDomainMap
     */
    private void putCache(TenantVo tenantVo, Map<String, String> tenantMap, Map<String, Set<String>> tenantIdRedisDomainMap) {
        if (tenantIdRedisDomainMap.containsKey(tenantVo.getTenantId())) {
            Set<String> redisTenantKeySet = tenantIdRedisDomainMap.get(tenantVo.getTenantId());
            if (redisTenantKeySet != null) {
                redisTemplate.opsForHash().delete(TENANT_KEY, redisTenantKeySet.toArray(new String[redisTenantKeySet.size()]));
            }
        }
        if (StringUtils.isNotBlank(tenantVo.getDomain())) {
            tenantMap.put(getTenantRedisKey(tenantVo), JSON.toJSONString(tenantVo));
        }
        if (StringUtils.isNotBlank(tenantVo.getTenantDomain())) {
            tenantMap.put(getTenantDomainRedisKey(tenantVo), JSON.toJSONString(tenantVo));
        }
    }

    public void delTenant(TenantVo tenantVo) {
        tenantLocalCache.remove(getTenantRedisKey(tenantVo));
        tenantLocalCache.remove(getTenantDomainRedisKey(tenantVo));
        redisTemplate.opsForHash().delete(TENANT_KEY, getTenantDomainRedisKey(tenantVo), getTenantDomainRedisKey(tenantVo));
    }

    /**
     * 获取redis中租户缓存Map 返回key 租户ID value redis 域名信息
     * @return
     */
    public Map<String, Set<String>> redisKeyCacheTenantIdMap() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(TENANT_KEY, ScanOptions.scanOptions().match("*").count(500).build());
        Map<String, Set<String>> tenantMap = Maps.newConcurrentMap();
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> currNext = cursor.next();
            String tenantVoStr = (String)currNext.getValue();
            TenantVo tenantVo = JSONObject.parseObject(tenantVoStr, TenantVo.class);
            if (tenantMap.containsKey(tenantVo.getTenantId())) {
                Set<String> redisTenantKeySet = tenantMap.get(tenantVo.getTenantId());
                redisTenantKeySet.add(getTenantRedisKey(tenantVo));
                redisTenantKeySet.add(getTenantDomainRedisKey(tenantVo));
            } else {
                Set<String> redisTenantKeySet = new HashSet<>();
                redisTenantKeySet.add(getTenantRedisKey(tenantVo));
                redisTenantKeySet.add(getTenantDomainRedisKey(tenantVo));
                tenantMap.put(tenantVo.getTenantId(), redisTenantKeySet);
            }
        }
        return tenantMap;
    }

    private void loadLocalCache() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(TENANT_KEY, ScanOptions.scanOptions().match("*").count(500).build());
        Map<String, String> tenantMap = Maps.newConcurrentMap();
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> currNext = cursor.next();
            String tenantKey = (String)currNext.getKey();
            String tenantVoStr = (String)currNext.getValue();
            tenantMap.put(tenantKey, tenantVoStr);
        }
        tenantLocalCache.clear();
        tenantLocalCache.putAll(tenantMap);
    }

    private String getTenantRedisKey(TenantVo tenantVo) {
        return TENANT_PREFIX  + "_" + tenantVo.getDomain();
    }

    private String getTenantDomainRedisKey(TenantVo tenantVo) {
        return TENANT_PREFIX  + "_" + tenantVo.getTenantDomain();
    }

    public static ConcurrentHashMap<String, String> getTenantLocalCache() {
        return tenantLocalCache;
    }

    public static String getTenantKey(String domain) {
        return TENANT_PREFIX  + "_" + domain;
    }
}